﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using ExpressionEvaluator.Extended;

namespace ExpressionEvaluator
{
	/// <summary>
	/// Evaluator of code to lambda expression.
	/// </summary>
	public class ExpressionEval
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="ExpressionEval"/> class.
		/// </summary>
		public ExpressionEval()
		{
			Parameters = new List<ParameterExpression>();
			_NamespacePatterns = new List<string>(1);
			_Assemblies = new List<string>();
			_Extensions = new List<ExtensionInfo>();
			// add system namespace for lookup for system types
			AddLookupNamespace("System");
			AddLookupAssembly("mscorlib.dll");
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ExpressionEval"/> class.
		/// </summary>
		/// <param name="code">The code.</param>
		public ExpressionEval(string code)
			: this()
		{
			Code = code;
		}

		/// <summary>
		/// List of namespace patterns for resolving types.
		/// </summary>
		private List<string> _NamespacePatterns;

		/// <summary>
		/// List of assemblies names for resolving types;
		/// </summary>
		private List<string> _Assemblies;


		/// <summary>
		/// List of extension informations for resolving extension for specified types.
		/// </summary>
		private List<ExtensionInfo> _Extensions;

		/// <summary>
		/// Gets or sets the code.
		/// </summary>
		/// <value>The code.</value>
		public string Code
		{
			get;
			set;
		}

		/// <summary>
		/// Gets the parameters for expression evaluation.
		/// </summary>
		/// <value>The parameters for expression evaluation.</value>
		public List<ParameterExpression> Parameters
		{
			get;
			private set;
		}

		/// <summary>
		/// Sets the code.
		/// </summary>
		/// <param name="code">The code.</param>
		/// <returns>This instance for fluent interface.</returns>
		public ExpressionEval SetCode(string code)
		{
			Code = code;
			return this;
		}

		/// <summary>
		/// Adds the parameter to expression eval.
		/// </summary>
		/// <param name="param">The param.</param>
		/// <returns>This instance for fluent interface.</returns>
		public ExpressionEval AddParam(ParameterExpression param)
		{
			Parameters.Add(param);
			return this;
		}

		/// <summary>
		/// Adds the param.
		/// </summary>
		/// <typeparam name="T">Type of parameter.</typeparam>
		/// <param name="paramName">Name of the param.</param>
		/// <returns>This instance for fluent interface.</returns>
		public ExpressionEval AddParam<T>(string paramName)
		{
			Parameters.Add(Expression.Parameter(typeof(T), paramName));
			return this;
		}

		/// <summary>
		/// Adds the lookup namespace.
		/// </summary>
		/// <param name="namespaceName">Name of the namespace.</param>
		/// <returns>This instance for fluent interface.</returns>
		public ExpressionEval AddLookupNamespace(string namespaceName)
		{
			AddLookupNamespace(namespaceName, null);
			return this;
		}

		/// <summary>
		/// Adds the lookup namespace.
		/// </summary>
		/// <param name="namespaceName">Name of the namespace.</param>
		/// <param name="assembly">The assembly.</param>
		/// <returns>This instance for fluent interface.</returns>
		public ExpressionEval AddLookupNamespace(string namespaceName, string assembly)
		{
			namespaceName += ".{0}";
			if (!string.IsNullOrEmpty(assembly))
			{
				AddLookupAssembly(assembly);
				namespaceName += ", " + assembly;
			}
			if (!_NamespacePatterns.Contains(namespaceName))
			{
				_NamespacePatterns.Add(namespaceName);
			}
			return this;
		}

		/// <summary>
		/// Adds the extension info for extension method lookup.
		/// </summary>
		/// <param name="namespaceName">Name of the namespace.</param>
		/// <param name="extensionType">Type of the extension.</param>
		/// <returns></returns>
		public ExpressionEval AddExtension(string namespaceName, string extensionType)
		{
			_Extensions.Add(new ExtensionInfo
			{
				NamespaceName = namespaceName,
				TypeName = extensionType
			});
			return this;
		}

		/// <summary>
		/// Adds the lookup assembly.
		/// </summary>
		/// <param name="assembly">The assembly.</param>
		/// <returns>This instance for fluent interface.</returns>
		public ExpressionEval AddLookupAssembly(string assembly)
		{
			_Assemblies.Add(assembly);
			return this;
		}

		/// <summary>
		/// Builds the tree.
		/// </summary>
		/// <returns></returns>
		internal Expression BuildTree()
		{
			return ExpressionTreeBuilder.BuildTree(
				EvalParser.ParseCode(Code),
				_NamespacePatterns,
				_Assemblies,
				_Extensions,
				Parameters.ToArray());
			
		}

		/// <summary>
		/// Evaluates this instance.
		/// </summary>
		/// <returns>Generated lambda expression from code</returns>
		public Expression<TDelegate> Eval<TDelegate>()
		{
			// add calling assembly for automatic resolve calling types
			AddLookupAssembly(Assembly.GetCallingAssembly().FullName);
			return Expression.Lambda<TDelegate>(BuildTree(), Parameters.ToArray());
		}

		/// <summary>
		/// Evaluates this instance without specifiing delegate type.
		/// </summary>
		/// <returns>Generated lambda expression from code</returns>
		public LambdaExpression EvalLambda()
		{
			// add calling assembly for automatic resolve calling types
			AddLookupAssembly(Assembly.GetCallingAssembly().FullName);
			return Expression.Lambda(BuildTree(), Parameters.ToArray());
		}

	}
}
